/* SPDX-License-Identifier: GPL-2.0 */
/*
 * Copyright (C) 2023. Huawei Technologies Co., Ltd. All rights reserved.
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
 * only version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 */

#include <linux/time.h>
#include <linux/fs_struct.h>
#include <linux/statfs.h>
#include <linux/wait.h>
#include <linux/version.h>
#include <asm-generic/ioctls.h>
#include <asm-generic/termbits.h>

#include "conn.h"
#include "qtfs-mod.h"
#include "req.h"
#include "log.h"

static void qtfs_fifo_put_file(struct file *file)
{
	struct private_data *priv;
	struct qtfs_conn_var_s *pvar;
	if (file == NULL || (priv = file->private_data) == NULL || (pvar = (struct qtfs_conn_var_s *)priv->priv) == NULL) {
		qtfs_err("fifo private data invalid to put");
		return;
	}
	qtfs_fifo_put_param(pvar);
	file->private_data = NULL;
	return;
}

int qtfs_fifo_open(struct inode *inode, struct file *file)
{
	struct kvec vec_save;
	unsigned int sendmax_save;
	struct qtreq *req;
	struct qtreq_fifo_open *fiforeq;
	struct qtrsp_fifo_open *rsp;
	struct qtfs_conn_var_s *pvar = NULL;
	struct private_data *priv;
	int ret;

	priv = (struct private_data *)kmalloc(sizeof(struct private_data), GFP_KERNEL);
	if (priv == NULL) {
		qtfs_err("qtfs fifo open kmalloc failed.");
		return -ENOMEM;
	}

	req = (struct qtreq *)kmalloc(sizeof(struct qtreq) + sizeof(struct qtreq_fifo_open), GFP_KERNEL);
	if (req == NULL) {
		qtfs_err("get fifo open memory failed.");
		kfree(priv);
		return -ENOMEM;
	}
	memset(req, 0, sizeof(struct qtreq) + sizeof(struct qtreq_fifo_open));

	pvar = qtfs_fifo_get_param();
	if (pvar == NULL) {
		qtfs_err("fifo get param failed.");
		kfree(req);
		kfree(priv);
		return -EINVAL;
	}
	fiforeq = (struct qtreq_fifo_open *)req->data;

	if (qtfs_fullname(fiforeq->path, file->f_path.dentry, sizeof(fiforeq->path)) < 0) {
		qtfs_err("qtfs fifo fullname failed");
		kfree(req);
		kfree(priv);
		qtfs_fifo_put_param(pvar);
		return -EINVAL;
	}

	fiforeq->flags = file->f_flags;
	fiforeq->mode = file->f_mode;
	qtfs_debug("fifo open path:%s size req:%lu size open:%lu, flags:%u mode%u",
				fiforeq->path, sizeof(struct qtreq), QTFS_SEND_SIZE(struct qtreq_fifo_open, fiforeq->path),
				fiforeq->flags, fiforeq->mode);
	vec_save = pvar->vec_send;
	sendmax_save = pvar->send_max;
	pvar->vec_send.iov_base = req;
	pvar->send_max = sizeof(struct qtreq) + sizeof(struct qtreq_fifo_open);
	rsp = qtfs_remote_run(pvar, QTFS_REQ_OPEN, QTFS_SEND_SIZE(struct qtreq_fifo_open, fiforeq->path));
	pvar->vec_send = vec_save;
	pvar->send_max = sendmax_save;

	if (IS_ERR_OR_NULL(rsp) || rsp->err != 0) {
		ret = IS_ERR_OR_NULL(rsp) ? -EFAULT : -rsp->err;
		qtfs_fifo_put_param(pvar);
		qtfs_err("qtfs fifo open :%s failed mode:%o flag:%x", fiforeq->path, fiforeq->mode, fiforeq->flags);
		kfree(req);
		kfree(priv);
		return ret;
	}
	kfree(req);
	priv->fd = rsp->fd;
	priv->priv = pvar;
	WARN_ON(file->private_data);
	file->private_data = priv;
	return 0;
}

ssize_t qtfs_fifo_readiter(struct kiocb *kio, struct iov_iter *iov)
{
	struct private_data *priv;
	struct qtfs_conn_var_s *pvar;
	struct qtreq_fifo_read *req;
	struct qtrsp_fifo_read *rsp;
	int total = 0;
	int ret;

	if (sigismember(&current->pending.signal, SIGURG)) {
		qtfs_err("signal SIGURG return eintr");
		return -EINTR;
	}
	if (!kio || !kio->ki_filp || (priv = kio->ki_filp->private_data) == NULL || (pvar = (struct qtfs_conn_var_s *)priv->priv) == NULL || !virt_addr_valid(pvar)) {
		qtfs_err("invalid fifo read req, private data is invalid");
		return -EFAULT;
	}
	req = pvar->conn_ops->get_conn_msg_buf(pvar, QTFS_SEND);
	req->len = iov_iter_count(iov);
	pvar->vec_recv.iov_len = QTFS_MSG_HEAD_LEN + sizeof(struct qtrsp_fifo_read);
	qtfs_info("fifo readiter len:%llu", req->len);
	rsp = qtfs_remote_run(pvar, QTFS_REQ_READITER, sizeof(struct qtreq_fifo_read));
	if (IS_ERR_OR_NULL(rsp) || rsp->err != 0) {
		qtfs_err("remote run failed. or errno:%d", IS_ERR_OR_NULL(rsp) ? -1 : rsp->err);
		//return -EFAULT;
		return (rsp == NULL) ? -EFAULT : (ssize_t)rsp; 
	}

	while (total < rsp->len) {
		ret = pvar->conn_ops->conn_recv_iter(&pvar->conn_var, iov, false);
		if (ret == -EAGAIN)
			continue;

		if (ret <= 0) {
			qtfs_err("recv iter from conn module ret:%d", ret);
			break;
		}
		iov->iov_offset += ret;
		total += ret;
	}
	qtfs_info("fifo readiter over, total:%d, rsplen:%llu", total, rsp->len);
	return total;
}

ssize_t qtfs_fifo_writeiter(struct kiocb *kio, struct iov_iter *iov)
{
	struct private_data *priv;
	struct qtfs_conn_var_s *pvar;
	struct qtreq_fifo_write *req;
	struct qtrsp_fifo_write *rsp;

	if (sigismember(&current->pending.signal, SIGURG)) {
		qtfs_err("signal SIGURG return eintr");
		return -EINTR;
	}
	if (!kio || !kio->ki_filp || (priv = kio->ki_filp->private_data) == NULL || (pvar = (struct qtfs_conn_var_s *)priv->priv) == NULL || !virt_addr_valid(pvar)) {
		qtfs_err("invalid fifo write req, private data is invalid");
		return -EFAULT;
	}
	req = pvar->conn_ops->get_conn_msg_buf(pvar, QTFS_SEND);
	req->len = iov_iter_count(iov);
	pvar->vec_recv.iov_len = QTFS_MSG_HEAD_LEN + sizeof(struct qtrsp_fifo_write);
	pvar->iov_send = iov;
	rsp = qtfs_remote_run(pvar, QTFS_REQ_WRITE, sizeof(struct qtreq_fifo_write));
	if (IS_ERR_OR_NULL(rsp) || rsp->err != 0) {
		qtfs_err("fifo write remote run failed, or errno:%d", IS_ERR_OR_NULL(rsp) ? -1 : rsp->err);
		return (rsp == NULL) ? -EFAULT : (ssize_t)rsp;
	}
	qtfs_info("fifo write over err:%d len:%llu", rsp->err, rsp->len);
	return rsp->len;
}

int qtfs_fifo_release(struct inode *inode, struct file *file)
{
	struct private_data *priv;
	struct qtfs_conn_var_s *pvar;
	struct qtrsp_fifo_close *rsp = NULL;

	if (file == NULL || (priv = file->private_data) == NULL || (pvar = (struct qtfs_conn_var_s *)priv->priv) == NULL) {
		qtfs_err("invalid fifo write req, private data is invalid");
		return -EFAULT;
	}
	pvar->vec_recv.iov_len = pvar->recv_max;
	rsp = qtfs_remote_run(pvar, QTFS_REQ_CLOSE, 0);
	if (IS_ERR_OR_NULL(rsp)) {
		qtfs_err("fifo close failed");
	}
	qtfs_info("fifo release req over");
	qtfs_fifo_put_file(file);
	return 0;
}

__poll_t
qtfs_poll(struct file *filp, poll_table *wait)
{
	struct qtfs_inode_priv *priv;
	__poll_t mask = 0;
	struct list_head *p;
	struct qtfs_conn_var_s *pvar;
	struct qtreq_poll *req;
	struct qtrsp_poll *rsp;
	struct private_data *fpriv;

	if (!filp || !filp->f_inode || !(priv = filp->f_inode->i_private) || !(fpriv = (struct private_data *)filp->private_data) || fpriv->fd < 0) {
		qtfs_err("fifo poll priv file invalid.");
		return 0;
	}

	poll_wait(filp, &priv->readq, wait);
	p = &priv->readq.head;

	pvar = qtfs_conn_get_param();
	if (pvar == NULL) {
		qtfs_err("qtfs fifo poll get param failed.");
		return 0;
	}
	req = pvar->conn_ops->get_conn_msg_buf(pvar, QTFS_SEND);
	req->fd = fpriv->fd;
	rsp = qtfs_remote_run(pvar, QTFS_REQ_FIFOPOLL, sizeof(struct qtreq_poll));
	if (IS_ERR_OR_NULL(rsp)) {
		qtfs_conn_put_param(pvar);
		return 0;
	}
	if (rsp->ret == QTFS_ERR) {
		qtfs_err("qtfs fifo poll remote run error.");
		qtfs_conn_put_param(pvar);
		return 0;
	}
	mask = rsp->mask;

	qtfs_info("fifo poll success mask:%x", mask);
	qtfs_conn_put_param(pvar);
	return mask;
}

struct file_operations qtfsfifo_ops = {
	.read_iter = qtfs_fifo_readiter,
	.write_iter = qtfs_fifo_writeiter,
	.open = qtfs_fifo_open,
	.release = qtfs_fifo_release,
	.llseek = no_llseek,
	.poll = qtfs_poll,
};
